using Sage.CloudStorage.Qiniu.Models;

namespace Sage.CloudStorage.Qiniu.Auth
{
    /// <summary>
    /// 七牛云上传策略工厂类
    /// 提供便捷的静态方法来创建不同类型的上传策略和上传认证提供者
    /// </summary>
    /// <remarks>
    /// **工厂模式优势**：
    /// - **简化创建过程**：隐藏复杂的上传策略创建逻辑，提供简洁的API
    /// - **类型安全保证**：为不同场景提供专门的创建方法，避免配置错误
    /// - **最佳实践内置**：内置了各种场景的最佳配置实践
    /// - **易于扩展维护**：集中管理上传策略创建逻辑，便于后续功能扩展
    /// 
    /// **支持的策略类型**：
    /// - **基本上传策略**：允许上传到指定存储空间，不限制资源键
    /// - **指定键上传策略**：只允许上传到指定存储空间的指定资源键
    /// - **前缀上传策略**：只允许上传到指定存储空间的指定前缀资源键
    /// - **自定义上传策略**：完全自定义的上传策略，支持所有参数
    /// - **高级上传策略**：提供常用高级参数的便捷设置
    /// 
    /// **适用场景**：
    /// - 不同业务场景的文件上传需求
    /// - 需要精确控制上传行为的场景
    /// - 批量文件上传的策略生成
    /// - 动态上传策略的创建
    /// </remarks>
    public static class QiniuUploadPolicyFactory
    {
        /// <summary>
        /// 创建基本上传策略的认证提供者
        /// 允许上传到指定存储空间，不限制资源键，适合通用上传场景
        /// </summary>
        /// <param name="qiniuKey">七牛云密钥信息</param>
        /// <param name="bucket">存储空间名称</param>
        /// <param name="expireInSeconds">过期时间（秒），默认3600秒（1小时）</param>
        /// <returns>配置好的上传认证提供者</returns>
        /// <exception cref="ArgumentNullException">当qiniuKey为null时抛出</exception>
        /// <exception cref="ArgumentException">当bucket为null或空时抛出</exception>
        /// <exception cref="ArgumentOutOfRangeException">当expireInSeconds小于等于0时抛出</exception>
        /// <remarks>
        /// **适用场景**：
        /// - **通用文件上传**：允许客户端上传任意文件到指定存储空间
        /// - **用户头像上传**：用户可以上传任意格式的头像文件
        /// - **文档管理系统**：允许上传各种类型的文档文件
        /// - **批量上传场景**：不需要预先指定文件名的批量上传
        /// - **移动端上传**：移动应用的通用文件上传功能
        /// 
        /// **策略特点**：
        /// - **灵活命名**：不限制文件名，可由客户端指定或系统自动生成
        /// - **插入模式**：默认只能新增文件，不能覆盖同名文件
        /// - **安全保障**：如果存在同名文件且内容不同，上传会失败
        /// - **幂等操作**：如果存在同名文件且内容相同，上传返回成功
        /// 
        /// **注意事项**：
        /// - 此策略下，文件只能新增，不能覆盖已存在的同名文件
        /// - 如果需要覆盖文件，请使用指定键的上传策略
        /// - 客户端需要处理文件名冲突的情况
        /// - 建议配合唯一文件名生成策略使用
        /// 
        /// **使用示例**：
        /// ```csharp
        /// var qiniuKey = new QiniuKey("your_access_key", "your_secret_key");
        /// 
        /// // 基本用法（1小时过期）
        /// var auth = QiniuUploadPolicyFactory.CreateBasicUploadAuth(qiniuKey, "my-bucket");
        /// 
        /// // 自定义过期时间（2小时）
        /// var longAuth = QiniuUploadPolicyFactory.CreateBasicUploadAuth(
        ///     qiniuKey, "my-bucket", 7200);
        /// ```
        /// 
        /// **最佳实践**：
        /// - 合理设置过期时间，平衡安全性和用户体验
        /// - 客户端应实现文件名去重机制
        /// - 可配合前端文件选择器使用
        /// - 建议添加客户端文件类型和大小验证
        /// </remarks>
        public static QiniuAuthProvider CreateBasicUploadAuth(QiniuKey qiniuKey, string bucket, int expireInSeconds = 3600)
        {
            ArgumentNullException.ThrowIfNull(qiniuKey, nameof(qiniuKey));
            ArgumentException.ThrowIfNullOrEmpty(bucket, nameof(bucket));
            if (expireInSeconds <= 0)
                throw new ArgumentOutOfRangeException(nameof(expireInSeconds), "过期时间必须大于0");

            var policy = UploadPolicy.CreateBasicPolicy(bucket, expireInSeconds);
            return new QiniuAuthProvider(qiniuKey, policy.ToJson());
        }

        /// <summary>
        /// 创建指定资源键的上传策略认证提供者
        /// 只允许上传到指定存储空间的指定资源键，适合精确控制的场景
        /// </summary>
        /// <param name="qiniuKey">七牛云密钥信息</param>
        /// <param name="bucket">存储空间名称</param>
        /// <param name="key">资源键名（文件的完整路径）</param>
        /// <param name="expireInSeconds">过期时间（秒），默认3600秒（1小时）</param>
        /// <param name="insertOnly">是否仅允许新增，不允许覆盖，默认false</param>
        /// <returns>配置好的上传认证提供者</returns>
        /// <exception cref="ArgumentNullException">当qiniuKey为null时抛出</exception>
        /// <exception cref="ArgumentException">当bucket或key为null或空时抛出</exception>
        /// <exception cref="ArgumentOutOfRangeException">当expireInSeconds小于等于0时抛出</exception>
        /// <remarks>
        /// **适用场景**：
        /// - **精确文件替换**：需要替换或更新已存在的特定文件
        /// - **配置文件上传**：上传到固定路径的配置文件
        /// - **版本控制**：需要覆盖旧版本文件的场景
        /// - **单文件上传**：明确知道目标文件路径的单文件上传
        /// - **系统文件更新**：系统级文件的更新和维护
        /// 
        /// **策略特点**：
        /// - **路径固定**：只能上传到指定的完整路径
        /// - **覆盖控制**：可以选择是否允许覆盖已存在的文件
        /// - **安全精确**：限制上传范围，提高安全性
        /// - **权限明确**：明确指定允许操作的文件
        /// 
        /// **覆盖行为**：
        /// - **insertOnly=false（默认）**：允许覆盖已存在的同名文件
        /// - **insertOnly=true**：仅允许新增，如果文件已存在则上传失败
        /// 
        /// **注意事项**：
        /// - 资源键需要包含完整的文件路径，包括目录结构
        /// - 如果设置insertOnly=true，已存在同名文件时上传会失败
        /// - 适合文件路径可预测的场景
        /// - 不适合批量上传或文件名动态生成的场景
        /// 
        /// **使用示例**：
        /// ```csharp
        /// var qiniuKey = new QiniuKey("your_access_key", "your_secret_key");
        /// 
        /// // 允许覆盖的上传（默认行为）
        /// var auth = QiniuUploadPolicyFactory.CreateKeyUploadAuth(
        ///     qiniuKey, "my-bucket", "documents/report.pdf");
        /// 
        /// // 仅允许新增，不允许覆盖
        /// var insertOnlyAuth = QiniuUploadPolicyFactory.CreateKeyUploadAuth(
        ///     qiniuKey, "my-bucket", "photos/avatar.jpg", 3600, true);
        /// 
        /// // 长时效的文件替换（4小时）
        /// var longAuth = QiniuUploadPolicyFactory.CreateKeyUploadAuth(
        ///     qiniuKey, "my-bucket", "config/app.json", 14400);
        /// ```
        /// 
        /// **最佳实践**：
        /// - 合理使用insertOnly参数，避免意外覆盖重要文件
        /// - 文件路径应符合业务逻辑和组织结构
        /// - 可配合版本管理系统使用
        /// - 建议记录文件更新日志
        /// </remarks>
        public static QiniuAuthProvider CreateKeyUploadAuth(QiniuKey qiniuKey, string bucket, string key, int expireInSeconds = 3600, bool insertOnly = false)
        {
            ArgumentNullException.ThrowIfNull(qiniuKey, nameof(qiniuKey));
            ArgumentException.ThrowIfNullOrEmpty(bucket, nameof(bucket));
            ArgumentException.ThrowIfNullOrEmpty(key, nameof(key));

            if (expireInSeconds <= 0)
                throw new ArgumentOutOfRangeException(nameof(expireInSeconds), "过期时间必须大于0");

            var policy = UploadPolicy.CreateKeyPolicy(bucket, key, expireInSeconds, insertOnly);
            return new QiniuAuthProvider(qiniuKey, policy.ToJson());
        }

        /// <summary>
        /// 创建指定前缀的上传策略认证提供者
        /// 只允许上传到指定存储空间的指定前缀资源键，适合目录级别的控制
        /// </summary>
        /// <param name="qiniuKey">七牛云密钥信息</param>
        /// <param name="bucket">存储空间名称</param>
        /// <param name="keyPrefix">资源键前缀（目录路径）</param>
        /// <param name="expireInSeconds">过期时间（秒），默认3600秒（1小时）</param>
        /// <returns>配置好的上传认证提供者</returns>
        /// <exception cref="ArgumentNullException">当qiniuKey为null时抛出</exception>
        /// <exception cref="ArgumentException">当bucket或keyPrefix为null或空时抛出</exception>
        /// <exception cref="ArgumentOutOfRangeException">当expireInSeconds小于等于0时抛出</exception>
        /// <remarks>
        /// **适用场景**：
        /// - **目录级权限控制**：限制上传文件到特定目录或分类
        /// - **用户空间隔离**：每个用户只能上传到自己的目录
        /// - **项目文件管理**：按项目或模块组织文件上传
        /// - **多租户系统**：为不同租户提供独立的上传空间
        /// - **内容分类上传**：按内容类型（如images/、documents/）分类上传
        /// 
        /// **策略特点**：
        /// - **前缀限制**：只能上传到以指定前缀开头的路径
        /// - **目录控制**：提供目录级别的访问控制
        /// - **灵活命名**：在前缀范围内可以自由命名文件
        /// - **批量友好**：适合批量上传到同一目录的场景
        /// 
        /// **注意事项**：
        /// - 使用前缀策略时，无法覆盖上传（等同于insertOnly=true）
        /// - isPrefixalScope会被自动设置为1
        /// - 前缀通常以"/"结尾，表示目录结构
        /// - 适合有明确目录结构需求的场景
        /// 
        /// **前缀示例**：
        /// - `"users/123/"` - 用户123的个人文件夹
        /// - `"projects/alpha/"` - alpha项目的文件目录
        /// - `"images/2024/03/"` - 2024年3月的图片目录
        /// - `"documents/contracts/"` - 合同文档目录
        /// 
        /// **使用示例**：
        /// ```csharp
        /// var qiniuKey = new QiniuKey("your_access_key", "your_secret_key");
        /// 
        /// // 用户个人目录上传
        /// var userAuth = QiniuUploadPolicyFactory.CreatePrefixUploadAuth(
        ///     qiniuKey, "user-files", "users/12345/");
        /// 
        /// // 项目文件目录上传
        /// var projectAuth = QiniuUploadPolicyFactory.CreatePrefixUploadAuth(
        ///     qiniuKey, "project-data", "projects/website-v2/");
        /// 
        /// // 按日期分类的图片上传
        /// var imageAuth = QiniuUploadPolicyFactory.CreatePrefixUploadAuth(
        ///     qiniuKey, "images", $"photos/{DateTime.Now:yyyy/MM}/");
        /// ```
        /// 
        /// **最佳实践**：
        /// - 前缀设计应符合业务逻辑和文件组织结构
        /// - 建议使用层次化的目录结构
        /// - 可以根据用户权限动态生成前缀
        /// - 适合与权限管理系统集成使用
        /// </remarks>
        public static QiniuAuthProvider CreatePrefixUploadAuth(QiniuKey qiniuKey, string bucket, string keyPrefix, int expireInSeconds = 3600)
        {
            ArgumentNullException.ThrowIfNull(qiniuKey, nameof(qiniuKey));
            ArgumentException.ThrowIfNullOrEmpty(bucket, nameof(bucket));
            ArgumentException.ThrowIfNullOrEmpty(keyPrefix, nameof(keyPrefix));

            if (expireInSeconds <= 0)
                throw new ArgumentOutOfRangeException(nameof(expireInSeconds), "过期时间必须大于0");

            var policy = UploadPolicy.CreatePrefixPolicy(bucket, keyPrefix, expireInSeconds);
            return new QiniuAuthProvider(qiniuKey, policy.ToJson());
        }

        /// <summary>
        /// 创建自定义上传策略的认证提供者
        /// 使用完全自定义的上传策略，支持所有高级功能和参数配置
        /// </summary>
        /// <param name="qiniuKey">七牛云密钥信息</param>
        /// <param name="policy">自定义的上传策略对象</param>
        /// <returns>配置好的上传认证提供者</returns>
        /// <exception cref="ArgumentNullException">当qiniuKey或policy为null时抛出</exception>
        /// <exception cref="ArgumentException">当策略的必填字段无效时抛出</exception>
        /// <remarks>
        /// **适用场景**：
        /// - **复杂业务需求**：需要设置多个高级参数的复杂上传场景
        /// - **回调处理**：需要上传成功后进行服务器回调的场景
        /// - **数据处理**：需要上传后自动进行转码、截图等处理
        /// - **精细控制**：需要对文件类型、大小等进行精确限制
        /// - **企业级应用**：需要完整功能配置的企业级文件上传
        /// 
        /// **支持的高级功能**：
        /// - **回调机制**：上传成功后向指定URL发送回调请求
        /// - **数据处理**：自动触发转码、截图、水印等处理任务
        /// - **返回控制**：自定义上传成功后的返回内容
        /// - **文件限制**：精确控制文件大小、类型、存储类型等
        /// - **安全控制**：设置上传权限、覆盖策略等
        /// 
        /// **注意事项**：
        /// - 使用此方法时，需要确保上传策略的必填字段已正确设置
        /// - 至少需要设置Scope（目标存储空间）和Deadline（过期时间）字段
        /// - 复杂的策略配置需要对七牛云上传策略有深入了解
        /// - 建议先测试策略的有效性
        /// 
        /// **使用示例**：
        /// ```csharp
        /// var qiniuKey = new QiniuKey("your_access_key", "your_secret_key");
        /// 
        /// // 创建具有回调功能的上传策略
        /// var policy = new UploadPolicy 
        /// {
        ///     Scope = "my-bucket",
        ///     Deadline = (uint)DateTimeOffset.UtcNow.AddHours(1).ToUnixTimeSeconds(),
        ///     CallbackUrl = "https://api.example.com/upload/callback",
        ///     CallbackBody = "{\"key\":$(key),\"hash\":$(etag),\"size\":$(fsize)}",
        ///     CallbackBodyType = "application/json",
        ///     ReturnBody = "{\"success\":true,\"key\":$(key)}",
        ///     FsizeLimit = 10485760, // 10MB限制
        ///     MimeLimit = "image/*"   // 只允许图片
        /// };
        /// 
        /// var auth = QiniuUploadPolicyFactory.CreateCustomUploadAuth(qiniuKey, policy);
        /// 
        /// // 创建具有数据处理功能的策略
        /// var processPolicy = new UploadPolicy
        /// {
        ///     Scope = "media-bucket:video.mp4",
        ///     Deadline = (uint)DateTimeOffset.UtcNow.AddHours(2).ToUnixTimeSeconds(),
        ///     PersistentOps = "avthumb/mp4|saveas/bWVkaWE6dGh1bWIubXA0", // 转码处理
        ///     PersistentNotifyUrl = "https://api.example.com/process/notify"
        /// };
        /// 
        /// var processAuth = QiniuUploadPolicyFactory.CreateCustomUploadAuth(qiniuKey, processPolicy);
        /// ```
        /// 
        /// **最佳实践**：
        /// - 仔细阅读七牛云官方文档，了解各参数的作用
        /// - 在生产环境使用前进行充分测试
        /// - 合理设置回调URL和处理逻辑
        /// - 注意数据处理操作的计费规则
        /// </remarks>
        public static QiniuAuthProvider CreateCustomUploadAuth(QiniuKey qiniuKey, UploadPolicy policy)
        {
            ArgumentNullException.ThrowIfNull(qiniuKey, nameof(qiniuKey));
            ArgumentNullException.ThrowIfNull(policy, nameof(policy));

            if (string.IsNullOrEmpty(policy.Scope))
                throw new ArgumentException("上传策略的Scope不能为空", nameof(policy));

            if (policy.Deadline == 0)
                throw new ArgumentException("上传策略的Deadline不能为0", nameof(policy));

            return new QiniuAuthProvider(qiniuKey, policy.ToJson());
        }

        /// <summary>
        /// 创建高级上传策略的认证提供者
        /// 提供常用高级参数的便捷设置，同时保持配置的灵活性
        /// </summary>
        /// <param name="qiniuKey">七牛云密钥信息</param>
        /// <param name="bucket">存储空间名称</param>
        /// <param name="key">资源键名，可选。如果为null，则创建基本策略</param>
        /// <param name="keyPrefix">资源键前缀，可选。如果指定，则创建前缀策略</param>
        /// <param name="expireInSeconds">过期时间（秒），默认3600秒（1小时）</param>
        /// <param name="insertOnly">是否仅允许新增，不允许覆盖，默认false</param>
        /// <param name="fsizeLimit">文件大小限制（字节），可选</param>
        /// <param name="mimeLimit">MIME类型限制，可选</param>
        /// <param name="callbackUrl">上传回调URL，可选</param>
        /// <param name="callbackBody">上传回调内容，可选</param>
        /// <param name="returnBody">上传成功返回内容，可选</param>
        /// <returns>配置好的上传认证提供者</returns>
        /// <exception cref="ArgumentNullException">当qiniuKey为null时抛出</exception>
        /// <exception cref="ArgumentException">当bucket为null或空，或参数配置无效时抛出</exception>
        /// <exception cref="ArgumentOutOfRangeException">当expireInSeconds小于等于0时抛出</exception>
        /// <remarks>
        /// **适用场景**：
        /// - **快速配置**：需要快速配置常用高级参数的上传场景
        /// - **动态策略**：需要在代码中动态构建上传策略的场景
        /// - **标准化应用**：企业内部标准化的文件上传配置
        /// - **原型开发**：快速原型开发中的文件上传功能
        /// - **配置驱动**：基于配置文件驱动的上传策略生成
        /// 
        /// **参数优先级**：
        /// 1. 如果指定了keyPrefix，创建前缀策略（忽略key参数）
        /// 2. 如果指定了key但没有keyPrefix，创建指定键策略
        /// 3. 如果都没指定，创建基本策略
        /// 
        /// **高级参数说明**：
        /// - **fsizeLimit**：文件大小限制，单位字节，限制上传文件的最大大小
        /// - **mimeLimit**：MIME类型限制，如"image/*"或"image/jpeg;image/png"
        /// - **callbackUrl**：回调URL，上传成功后七牛云会向此URL发送POST请求
        /// - **callbackBody**：回调内容，发送给回调URL的请求体内容
        /// - **returnBody**：返回内容，直接返回给客户端的响应内容
        /// 
        /// **注意事项**：
        /// - callbackUrl和callbackBody必须同时设置或同时为空
        /// - 如果设置了callbackUrl但没有设置callbackBody，会提供默认的JSON回调内容
        /// - 回调功能需要确保回调URL可以正常访问
        /// - MIME类型限制会在服务端进行验证
        /// 
        /// **使用示例**：
        /// ```csharp
        /// var qiniuKey = new QiniuKey("your_access_key", "your_secret_key");
        /// 
        /// // 创建图片上传策略（带大小和类型限制）
        /// var imageAuth = QiniuUploadPolicyFactory.CreateAdvancedUploadAuth(
        ///     qiniuKey: qiniuKey,
        ///     bucket: "images",
        ///     key: "photos/avatar.jpg",
        ///     fsizeLimit: 5 * 1024 * 1024, // 5MB限制
        ///     mimeLimit: "image/jpeg;image/png",
        ///     returnBody: "{\"success\":true,\"url\":\"https://cdn.example.com/$(key)\"}"
        /// );
        /// 
        /// // 创建带回调功能的文档上传策略
        /// var docAuth = QiniuUploadPolicyFactory.CreateAdvancedUploadAuth(
        ///     qiniuKey: qiniuKey,
        ///     bucket: "documents",
        ///     keyPrefix: "contracts/",
        ///     fsizeLimit: 50 * 1024 * 1024, // 50MB限制
        ///     mimeLimit: "application/pdf;application/msword",
        ///     callbackUrl: "https://api.example.com/upload/callback",
        ///     callbackBody: "{\"action\":\"upload\",\"key\":$(key),\"size\":$(fsize)}"
        /// );
        /// 
        /// // 创建用户目录上传策略
        /// var userAuth = QiniuUploadPolicyFactory.CreateAdvancedUploadAuth(
        ///     qiniuKey: qiniuKey,
        ///     bucket: "user-files",
        ///     keyPrefix: $"users/{userId}/uploads/",
        ///     expireInSeconds: 7200, // 2小时
        ///     insertOnly: true,      // 不允许覆盖
        ///     fsizeLimit: 100 * 1024 * 1024 // 100MB限制
        /// );
        /// ```
        /// 
        /// **最佳实践**：
        /// - 根据文件类型设置合理的大小限制
        /// - 使用MIME类型限制提高安全性
        /// - 回调URL应该具有幂等性，能够处理重复请求
        /// - 合理设置过期时间，平衡安全性和用户体验
        /// - 在开发阶段充分测试各种参数组合
        /// </remarks>
        public static QiniuAuthProvider CreateAdvancedUploadAuth(
            QiniuKey qiniuKey,
            string bucket,
            string? key = null,
            string? keyPrefix = null,
            int expireInSeconds = 3600,
            bool insertOnly = false,
            long? fsizeLimit = null,
            string? mimeLimit = null,
            string? callbackUrl = null,
            string? callbackBody = null,
            string? returnBody = null)
        {
            ArgumentNullException.ThrowIfNull(qiniuKey, nameof(qiniuKey));
            ArgumentException.ThrowIfNullOrEmpty(bucket, nameof(bucket));

            if (expireInSeconds <= 0)
                throw new ArgumentOutOfRangeException(nameof(expireInSeconds), "过期时间必须大于0");

            // 根据参数决定创建哪种类型的策略
            UploadPolicy policy;

            // 参数优先级：keyPrefix > key > 基本策略
            if (!string.IsNullOrEmpty(keyPrefix))
            {
                // 创建前缀策略
                policy = UploadPolicy.CreatePrefixPolicy(bucket, keyPrefix, expireInSeconds);
            }
            else if (!string.IsNullOrEmpty(key))
            {
                // 创建指定键策略
                policy = UploadPolicy.CreateKeyPolicy(bucket, key, expireInSeconds, insertOnly);
            }
            else
            {
                // 创建基本策略
                policy = UploadPolicy.CreateBasicPolicy(bucket, expireInSeconds);
                if (insertOnly)
                    policy.InsertOnly = 1;
            }

            // 设置高级参数
            if (fsizeLimit.HasValue)
                policy.FsizeLimit = fsizeLimit.Value;

            if (!string.IsNullOrEmpty(mimeLimit))
                policy.MimeLimit = mimeLimit;

            // 处理回调相关参数
            if (!string.IsNullOrEmpty(callbackUrl))
            {
                policy.CallbackUrl = callbackUrl;

                // 如果设置了callbackUrl但没有设置callbackBody，提供一个默认值
                if (string.IsNullOrEmpty(callbackBody))
                    policy.CallbackBody = "{\"key\":$(key),\"hash\":$(etag),\"size\":$(fsize)}";
                else
                    policy.CallbackBody = callbackBody;

                // 默认使用JSON格式回调
                policy.CallbackBodyType = "application/json";
            }
            else if (!string.IsNullOrEmpty(callbackBody))
            {
                throw new ArgumentException("设置callbackBody时必须同时设置callbackUrl", nameof(callbackBody));
            }

            if (!string.IsNullOrEmpty(returnBody))
                policy.ReturnBody = returnBody;

            return new QiniuAuthProvider(qiniuKey, policy.ToJson());
        }
    }
}
